﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.ComponentModel;
using MJunction;
using Microsoft.Win32;

namespace L2Tools.Model
{
    public class L2Directory
    {
        #region const
        const string AnimationsName = "Animations";
        const string MapsName = "Maps";
        const string MusicName = "Music";
        const string SoundsName = "Sounds";
        const string StaticMeshesName = "StaticMeshes";
        const string SystemName = "System";
        const string SysTexturesName = "SysTextures";
        const string TexturesName = "Textures";
        const string VoiceName = "Voice";

        const string L2exe = @"\LineageII.exe";

        const string L2RegistryKey = @"SOFTWARE\NCSoft\L2Client_jp";
        const string Workingdir = "workingdir";
        public const string Backup = @"\Backup";
        const string DefaultSSDirectory = @"\LineageII";
        const string SystemDirectory = @"\System";
        const string Bitmap = ".bmp";
        const string Jpeg = ".jpg";
        #endregion

        #region Attribute
        private readonly ISynchronizeInvoke synchronizeInvoke;

        private string l2DirectoryPath = null;
        private string ramdiskDirectoryPath = null;
        private string ssRepositoryPath = null;

        private bool isAjust = false;

        private readonly IDictionary<string, L2SubDirectory> subDirectories = new Dictionary<string, L2SubDirectory>();

        /// <summary>
        /// SS監視を行うためのWatcher
        /// </summary>
        private FileSystemWatcher watcher = null;

        private MirroringJunctionManager manager = null;
        #endregion

        #region Property
        public string L2DirectoryPath
        {
            get { return l2DirectoryPath; }
            set { l2DirectoryPath = value; }
        }

        public string RamdiskDirectoryPath
        {
            get { return ramdiskDirectoryPath; }
            set { ramdiskDirectoryPath = value; }
        }
        public string SsRepositoryPath
        {
            get { return ssRepositoryPath; }
            set { ssRepositoryPath = value; }
        }
        public string BackupDirectoryPath
        {
            get
            {
                if (L2DirectoryPath != null)
                {
                    return L2DirectoryPath + Backup;
                }
                else
                {
                    return null;
                }
            }
        }
        public bool IsAjust
        {
            get { return isAjust; }
            set { isAjust = value; }
        }
        
        public L2SubDirectory Animations
        {
            get { return subDirectories[AnimationsName]; }
        }

        public L2SubDirectory Maps
        {
            get { return subDirectories[MapsName]; }
        }

        public L2SubDirectory Music
        {
            get { return subDirectories[MusicName]; }
        }

        public L2SubDirectory Sounds
        {
            get { return subDirectories[SoundsName]; }
        }

        public L2SubDirectory StaticMeshes
        {
            get { return subDirectories[StaticMeshesName]; }
        }

        public L2SubDirectory System
        {
            get { return subDirectories[SystemName]; }
        }

        public L2SubDirectory SysTextures
        {
            get { return subDirectories[SysTexturesName]; }
        }

        public L2SubDirectory Textures
        {
            get { return subDirectories[TexturesName]; }
        }

        public L2SubDirectory Voice
        {
            get { return subDirectories[VoiceName]; }
        }

        #endregion

        #region Constructor
        /// <summary>
        /// インスタンスを生成する。
        /// </summary>
        /// <param name="synchronizeInvoke">FileSystemWatcherにてファイルの監視を行う際に使用するISynchronizeInvoke</param>
        /// <exception cref="ArgumentNullException">
        /// 何れかの引数がnullだった場合にスローされる。
        /// </exception>
        public L2Directory(ISynchronizeInvoke synchronizeInvoke)
        {
            if (synchronizeInvoke == null)
            {
                throw new ArgumentNullException("synchronizeInvoke is null.");
            }
            subDirectories.Add(AnimationsName, new L2SubDirectory(this, AnimationsName));
            subDirectories.Add(MapsName, new L2SubDirectory(this, MapsName));
            subDirectories.Add(MusicName, new L2SubDirectory(this, MusicName));
            subDirectories.Add(SoundsName, new L2SubDirectory(this, SoundsName));
            subDirectories.Add(StaticMeshesName, new L2SubDirectory(this, StaticMeshesName));
            subDirectories.Add(SystemName, new L2SubDirectory(this, SystemName));
            subDirectories.Add(SysTexturesName, new L2SubDirectory(this, SysTexturesName));
            subDirectories.Add(TexturesName, new L2SubDirectory(this, TexturesName));
            subDirectories.Add(VoiceName, new L2SubDirectory(this, VoiceName));

            this.synchronizeInvoke = synchronizeInvoke;
        }

        #endregion

        #region Method
        /// <summary>
        /// ディレクトリ状態が正常か判定を行う。
        /// </summary>
        /// <returns></returns>
        public bool Validation()
        {
            if (!ValidationL2Directory())
            {
                return false;
            }
            else if (!ValidationRamdiskDirectory())
            {
                return false;
            }
            else if (!ValidationSsRepository())
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        public bool ValidationL2Directory()
        {
            if (!Directory.Exists(L2DirectoryPath))
            {
                return false;
            }
            else if (!Directory.Exists(L2DirectoryPath + SystemDirectory))
            {
                return false;
            }
            else if (!File.Exists(L2DirectoryPath + L2exe))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        public bool ValidationRamdiskDirectory()
        {
            if (!Directory.Exists(RamdiskDirectoryPath))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        public bool ValidationSsRepository()
        {
            if (!IsAjust)
            {
                return true;
            }
            else if(!Directory.Exists(SsRepositoryPath))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        public void Setup(ISetupListener listener)
        {
            if (BackupDirectoryPath == null)
            {
                // ちゃんとした例外処理は後回し
                throw new Exception("BackupDirectoryInfo is null.");
            }
            if (L2DirectoryPath == null)
            {
                // ちゃんとした例外処理は後回し
                throw new Exception("L2DirectoryInfo is null.");
            }
            if (RamdiskDirectoryPath == null)
            {
                // ちゃんとした例外処理は後回し
                throw new Exception("RamdiskDirectoryInfo is null.");
            }

            manager = new MirroringJunctionManager(BackupDirectoryPath, RamdiskDirectoryPath);
            // まず有効ディレクトリのJunctionリストを作成する。
            IList<MirroringJunction> newJunctions =　new List<MirroringJunction>();
            foreach (L2SubDirectory sub in subDirectories.Values)
            {
                MirroringJunction junction = sub.CreateMirroringJunction();
                if (junction != null)
                {
                    newJunctions.Add(junction);
                }
            }

            // Managerに設定済みで、現在無効なJunctionを削除する。
            MirroringJunction[] oldJunctions = manager.Junctions;
            foreach (MirroringJunction junction in oldJunctions)
            {
                if(!newJunctions.Contains(junction))
                {
                    manager.RemoveJunction(junction.Name);
                }
            }

            // Managerに未設定な新しいJunctionを追加する。
            foreach(MirroringJunction junction in newJunctions)
            {
                if(manager.GetMirroringJunction(junction.Name) == null)
                {
                    manager.AddMirroringJunction(junction);
                }
            }

            manager.Setup(listener);
        }
        public void TearDown()
        {
            using (MirroringJunctionManager tempManager =
                new MirroringJunctionManager(BackupDirectoryPath, RamdiskDirectoryPath))
            {
                foreach (L2SubDirectory sub in subDirectories.Values)
                {
                    MirroringJunction junction = sub.CreateMirroringJunctionForce();
                    if (junction != null)
                    {
                        tempManager.AddMirroringJunction(junction);
                    }
                }
                tempManager.TearDown();
            }
        }

        public void Start()
        {
            if (manager == null)
            {
                throw new Exception("手抜き例外処理。マネージャーがいないよ");
            }
            manager.StartMirroring();
            if (IsAjust)
            {
                StartAjustSS();
            }

            Process process = new Process();
            process.StartInfo.FileName = L2DirectoryPath + L2exe;
            process.StartInfo.WorkingDirectory = L2DirectoryPath;

            process.Start();
        }

        private void StartAjustSS()
        {
            if (watcher == null)
            {
                watcher = new FileSystemWatcher();
            }
            else
            {
                watcher.EnableRaisingEvents = false;
            }
            watcher.Path = L2DirectoryPath + SystemDirectory;
            watcher.NotifyFilter = NotifyFilters.FileName;
            watcher.Filter = "";
            watcher.SynchronizingObject = synchronizeInvoke;

            watcher.Created += new FileSystemEventHandler(watcherCreated);
            watcher.EnableRaisingEvents = true;
        }

        //イベントハンドラ
        private void watcherCreated(System.Object source,
            System.IO.FileSystemEventArgs e)
        {
            if (e.FullPath.EndsWith(Bitmap) || e.FullPath.EndsWith(Jpeg))
            {
                string dirName = String.Format("{0:yyyy年MM月dd日}", DateTime.Now);
                if (!Directory.Exists(SsRepositoryPath))
                {
                    Directory.CreateDirectory(SsRepositoryPath);
                }
                string dirPath = SsRepositoryPath + @"\" + dirName;
                if (!Directory.Exists(dirPath))
                {
                    Directory.CreateDirectory(dirPath);
                }
                string fileName = 
                    String.Format("{0:HH時mm分ss秒}", DateTime.Now)
                    + e.FullPath.Substring(e.FullPath.LastIndexOf('.'));

                Thread.Sleep(500);
                File.Move(e.FullPath, dirPath + @"\" + fileName);
            }
        }

        public void Stop()
        {
            if (manager == null)
            {
                throw new Exception("手抜き例外処理。マネージャーがいないよ");
            }
            watcher.EnableRaisingEvents = false;
            watcher = null;
            manager.StopMirroring();
        }
        /// <summary>
        /// 
        /// </summary>
        public L2DirectoryInfo Count()
        {
            L2DirectoryInfo result = new L2DirectoryInfo();

            foreach (L2SubDirectory dir in subDirectories.Values)
            {
                if (dir.Selected)
                {
                    L2DirectoryInfo subInfo = dir.Count();
                    result.Size += subInfo.Size;
                    result.FileCount += subInfo.FileCount;
                }
            }

            return result;
        }
        #endregion
    }
}
